package movee.spring.app.template.api.aspect;

import io.github.resilience4j.ratelimiter.RateLimiter;
import io.github.resilience4j.ratelimiter.RateLimiterRegistry;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import movee.spring.app.template.api.utils.ApiUtils;
import movee.spring.app.template.domain.api.ApiConstants;
import movee.spring.app.template.domain.api.ApiException;
import org.apache.commons.lang3.tuple.Pair;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.slf4j.MDC;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.lang.NonNull;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.validation.ConstraintViolationException;
import java.time.Duration;
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.UUID;

/**
 * 应用程序restful api 的AOP功能，实现日志记录、限流、响应时间测量等公共功能
 *
 * @author movee
 */
@Slf4j
public class BasicApiAspect {

    @Getter
    private RateLimiter rateLimiter = null;

    public Object apiAroundAdvise(final ProceedingJoinPoint jp) throws Throwable {
        Instant start = Instant.now();
        // 处理请求之前处理逻辑
        Pair<String, String> httpContext = beforeProceedForHttp(jp, start);
        // 处理请求
        Object result = doProceed(jp);
        // 处理请求之后的处理逻辑
        afterProceedForHttp(jp, httpContext.getLeft(), httpContext.getRight(), start);
        // 返回处理结果
        return result;
    }

    /**
     * 处理请求之前处理逻辑
     * @param jp 连接点，可获取调用信息
     * @param start 入口时间
     * @return 请求id和源ip
     */
    private Pair<String, String> beforeProceedForHttp(ProceedingJoinPoint jp, Instant start) {

        String requestId = "";
        String clientRealIp = "";

        // http请求信息
        RequestAttributes reqAttrs = RequestContextHolder.getRequestAttributes();
        // http context
        if (reqAttrs != null) {

            // 处理请求的方法签名
            MethodSignature methodSignature = (MethodSignature) jp.getSignature();
            // 方法名
            String targetName = methodSignature.getName();
            // 方法参数名列表
            String[] pNames = methodSignature.getParameterNames();
            // 方法值列表
            Object[] pValues = jp.getArgs();

            String params = formatMethodParameters(pNames, pValues);

            // 获取源ip
            HttpServletRequest httpRequest = ((ServletRequestAttributes) reqAttrs).getRequest();
            clientRealIp = ApiUtils.getClientRealIp(httpRequest);

            if (httpRequest.getAttribute(ApiConstants.X_API_REQUEST_ID) == null) {
                // 本次请求第一次调用，生成标识请求的唯一id
                requestId = UUID.randomUUID().toString();
                httpRequest.setAttribute(ApiConstants.X_API_REQUEST_ID, requestId);

                // 记录调用信息到http请求中，调用结束或异常时可以取出来
                httpRequest.setAttribute(ApiConstants.API_REQUEST_START, start);
                httpRequest.setAttribute(ApiConstants.API_REQUEST_ENTRY, targetName);

                // 记录日志时记录请求id
                MDC.put(ApiConstants.X_API_REQUEST_ID, requestId);

                // 获取处理请求的controller类的日志类，并记录一条日志
                Logger logger = LoggerFactory.getLogger(jp.getTarget().getClass());
                logger.info("clientRealIp: {}, start {}, parameters: {}", clientRealIp, targetName, params);
            }
        }

        return Pair.of(requestId, clientRealIp);
    }

    /**
     * 对请求进行限流，并处理请求
     * @param jp
     * @return
     * @throws Throwable
     */
    private Object doProceed(ProceedingJoinPoint jp) throws Throwable {
        Object result;
        if (rateLimiter == null) {
            result = jp.proceed();
        } else {
            // 开启了限流
            result = RateLimiter.decorateSupplier(rateLimiter, () -> {
                try {
                    return jp.proceed();
                } catch (Throwable t) {
                    if (t instanceof ApiException) {
                        throw (ApiException) t;
                    } else if (t instanceof ConstraintViolationException) {
                        throw (ConstraintViolationException) t;
                    } else {
                        throw new RuntimeException("run into a api exception", t);
                    }
                }
            }).get();
        }
        return result;
    }

    /**
     * 处理请求之后的处理逻辑
     * @param jp 连接点
     * @param requestId 请求id
     * @param clientRealIp 源ip
     * @param start 调用时间
     */
    private void afterProceedForHttp(ProceedingJoinPoint jp, String requestId, String clientRealIp, Instant start) {

        RequestAttributes reqAttrs = RequestContextHolder.getRequestAttributes();
        // http context
        if (reqAttrs != null) {
            HttpServletResponse httpResponse = ((ServletRequestAttributes) reqAttrs).getResponse();
            if (httpResponse != null) {
                // 为响应填充一些必要信息
                httpResponse.setHeader(ApiConstants.X_API_REQUEST_ID, requestId);
                String date = LocalDateTime.now().format(DateTimeFormatter.ISO_DATE_TIME);
                httpResponse.setHeader(ApiConstants.X_API_DATE, date);
                httpResponse.setHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE);

                // log info
                Duration duration = Duration.between(start, Instant.now());
                MethodSignature methodSignature = (MethodSignature) jp.getSignature();
                Logger logger = LoggerFactory.getLogger(jp.getTarget().getClass());
                logger.info("clientRealIp: {}, finish {}, duration time: {} ms",
                        clientRealIp, methodSignature.getName(), duration.toMillis());
            }
        }
    }

    /**
     * 格式化参数和值
     * @param pNames 参数
     * @param pValues 值
     * @return
     */
    private String formatMethodParameters(@NonNull final String[] pNames, @NonNull final Object[] pValues) {

        int len = Math.min(pNames.length, pValues.length);
        StringBuilder sb = new StringBuilder();
        int i = len;
        while (i > 1) {
            sb.append(String.format("%s=%s,", pNames[len - i], pValues[len - i]));
            i--;
        }

        if (i == 1) {
            sb.append(String.format("%s=%s", pNames[len - i], pValues[len - i]));
        } else {
            sb.append("none");
        }

        return sb.toString();

    }

    /**
     * 获取注册的限流器
     * @param registry 注册器
     */
    public void setRateLimiter(RateLimiterRegistry registry) {
        if (registry != null) {
            rateLimiter = registry.find("apiTotalLimit").orElse(null);
        }
    }

}
